package com.bsj.sipgateway.sip.transmit.request.impl;

import com.alibaba.fastjson.JSON;
import com.bsj.power.common.config.redis.RedisConstant;
import com.bsj.power.common.config.rocketmq.producer.BaseMqPushUtil;
import com.bsj.power.common.constant.AlarmEnum;
import com.bsj.power.common.constant.MqTopicAndTagConstants;
import com.bsj.power.common.constant.NumericalConstants;
import com.bsj.power.common.constant.SysLogTitleConst;
import com.bsj.power.common.def.dto.RedisDeviceInfo;
import com.bsj.power.common.def.entity.Fault;
import com.bsj.power.common.def.entity.alarmReport.AlarmReport;
import com.bsj.power.common.def.entity.gb28181.Device;
import com.bsj.power.common.def.entity.gb28181.DeviceAlarm;
import com.bsj.power.common.def.entity.gb28181.DeviceChannel;
import com.bsj.power.common.def.entity.log.CmdLog;
import com.bsj.power.common.def.entity.log.SysRecordLog;
import com.bsj.power.common.def.enums.BusinessType;
import com.bsj.power.common.def.enums.CmdLogEnum;
import com.bsj.power.common.def.enums.FileTypeEnum;
import com.bsj.power.common.def.mq.sdk.SdkAnnex;
import com.bsj.power.common.def.vo.WebSocketMsgVO;
import com.bsj.power.common.file.log.OperateLogSaveQueue;
import com.bsj.power.common.mapper.*;
import com.bsj.power.common.util.DateUtils;
import com.bsj.power.common.util.FileCreateUtil;
import com.bsj.power.common.util.SpringUtil;
import com.bsj.sipgateway.cache.DeviceCache;
import com.bsj.sipgateway.cache.ExpiredTimedCache;
import com.bsj.sipgateway.common.constant.Constants;
import com.bsj.sipgateway.common.def.RecordEmpty;
import com.bsj.sipgateway.common.def.RecordFile;
import com.bsj.sipgateway.common.def.RecordList;
import com.bsj.sipgateway.config.SipConfig;
import com.bsj.sipgateway.mq.producer.CommandUpProducer;
import com.bsj.sipgateway.mysql.bsj.vehicle.service.IVehicleService;
import com.bsj.sipgateway.sip.auth.DigestServerAuthenticationHelper;
import com.bsj.sipgateway.sip.bean.Command;
import com.bsj.sipgateway.sip.bean.DeviceAlarmMethodEnum;
import com.bsj.sipgateway.sip.bean.Position;
import com.bsj.sipgateway.sip.bean.SipMsgInfo;
import com.bsj.sipgateway.sip.event.EventPublisher;
import com.bsj.sipgateway.sip.session.CommandManager;
import com.bsj.sipgateway.sip.transmit.callback.CheckForAllRecordsThread;
import com.bsj.sipgateway.sip.transmit.callback.DeferredResultHolder;
import com.bsj.sipgateway.sip.transmit.cmd.impl.SIPCommander;
import com.bsj.sipgateway.sip.transmit.request.SIPRequestAbstractProcessor;
import com.bsj.sipgateway.util.ByteToXmlUtil;
import com.bsj.sipgateway.util.DateUtil;
import com.bsj.sipgateway.util.NumericUtil;
import com.bsj.sipgateway.util.RecordInfoUtil;
import com.github.pagehelper.util.StringUtil;
import gov.nist.javax.sip.RequestEventExt;
import gov.nist.javax.sip.address.AddressImpl;
import gov.nist.javax.sip.address.SipUri;
import gov.nist.javax.sip.message.SIPRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import redis.clients.bsj.JedisClusterBSJ;

import javax.sip.InvalidArgumentException;
import javax.sip.RequestEvent;
import javax.sip.SipException;
import javax.sip.header.FromHeader;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.io.ByteArrayInputStream;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;

import static com.bsj.power.common.config.aspect.LogAspect.LOG_ID;
import static com.bsj.sipgateway.util.XmlUtil.getText;

/**
 * @author bsj-chenjunkang
 * @Description:MESSAGE请求处理器
 * @date 2022/8/31
 */
@Slf4j
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public class MessageRequestProcessor extends SIPRequestAbstractProcessor {

    //缓存，key:deviceId_sn
    public static volatile ConcurrentHashMap<String, RecordList> recordListMap = new ConcurrentHashMap<>();

    private final ConcurrentLinkedQueue<SipMsgInfo> taskQueue = new ConcurrentLinkedQueue<>();

    // 接收到设备发送的catalog消息后，与缓存对比，不一样的信息修改缓存和db
    private final ConcurrentLinkedQueue<Device> deviceQueue = new ConcurrentLinkedQueue<>();

    private Executor taskExecutor;

    private AlarmReportMapper alarmReportMapper;

    private DeviceMapper deviceMapper;

    private SIPCommander cmder;

    private SipConfig sipConfig;

    private JedisClusterBSJ jedisClusterBSJ;

    private EventPublisher publisher;

    private BaseMqPushUtil baseMqPushUtil;

    private FaultMapper faultMapper;

    private String cvrIds;

    private boolean needAlarmIn;

    private DeviceChannelMapper deviceChannelMapper;

    public void setAnnexPath(String annexPath) {
        this.annexPath = annexPath;
    }

    private String annexPath;

    private IVehicleService vehicleService;

    private CommandUpProducer commandUpProducer;

    private DeferredResultHolder deferredResultHolder;

    private CmdLogMapper cmdLogMapper;

    private final static String CACHE_RECORDINFO_KEY = "CACHE_RECORDINFO_";

    private static final String MESSAGE_KEEP_ALIVE = "Keepalive";
    private static final String MESSAGE_CONFIG_DOWNLOAD = "ConfigDownload";
    private static final String MESSAGE_CATALOG = "Catalog";
    private static final String MESSAGE_DEVICE_INFO = "DeviceInfo";
    private static final String MESSAGE_ALARM = "Alarm";
    private static final String MESSAGE_RECORD_INFO = "RecordInfo";
    private static final String MESSAGE_MEDIA_STATUS = "MediaStatus";
    private static final String MESSAGE_BROADCAST = "Broadcast";
    private static final String MESSAGE_DEVICE_STATUS = "DeviceStatus";
    private static final String MESSAGE_DEVICE_CONTROL = "DeviceControl";
    private static final String MESSAGE_DEVICE_CONFIG = "DeviceConfig";
    private static final String MESSAGE_MOBILE_POSITION = "MobilePosition";
    // private static final String MESSAGE_MOBILE_POSITION_INTERVAL = "Interval";
    private static final String MESSAGE_PRESET_QUERY = "PresetQuery";

    /**
     * 处理MESSAGE请求
     *
     * @param evt
     */
    @Override
    public void process(RequestEvent evt) {

        try {
//            Element rootElement = getRootElement(evt);
            Element rootElement = ByteToXmlUtil.byteToXml(evt.getRequest().getRawContent()).getRootElement();
            String cmd = getText(rootElement, "CmdType");

            if (MESSAGE_KEEP_ALIVE.equals(cmd)) {
//                log.info("接收到KeepAlive消息");
                processMessageKeepAlive(evt);
            } else if (MESSAGE_CONFIG_DOWNLOAD.equals(cmd)) {
                log.info("接收到ConfigDownload消息");
                processMessageConfigDownload(evt);
            } else if (MESSAGE_CATALOG.equals(cmd)) {
//                log.info("接收到Catalog消息");
                processMessageCatalogList(evt);
            } else if (MESSAGE_DEVICE_INFO.equals(cmd)) {
                log.info("接收到DeviceInfo消息");
                processMessageDeviceInfo(evt);
            } else if (MESSAGE_DEVICE_STATUS.equals(cmd)) {
                log.info("接收到DeviceStatus消息");
                processMessageDeviceStatus(evt);
            } else if (MESSAGE_DEVICE_CONTROL.equals(cmd)) {
                log.info("接收到DeviceControl消息");
                processMessageDeviceControl(evt);
            } else if (MESSAGE_DEVICE_CONFIG.equals(cmd)) {
                log.info("接收到DeviceConfig消息");
                processMessageDeviceConfig(evt);
            } else if (MESSAGE_ALARM.equals(cmd)) {
                log.info("接收到Alarm消息");
                processMessageAlarm(evt);
            } else if (MESSAGE_RECORD_INFO.equals(cmd)) {
                log.info("接收到RecordInfo消息");
                processMessageRecordInfo(evt);
            } else if (MESSAGE_MEDIA_STATUS.equals(cmd)) {
                log.info("接收到MediaStatus消息");
                processMessageMediaStatus(evt);
            } else if (MESSAGE_MOBILE_POSITION.equals(cmd)) {
                log.info("接收到MobilePosition消息");
                processMessageMobilePosition(evt);
            } else if (MESSAGE_PRESET_QUERY.equals(cmd)) {
                log.info("接收到PresetQuery消息");
                processMessagePresetQuery(evt);
            } else if (MESSAGE_BROADCAST.equals(cmd)) {
                log.info("接收到Broadcast消息");
                processMessageBroadcast(evt);
            } else {
                log.info("接收到消息：" + cmd);
                responseAck(evt);
            }
        } catch (SipException | InvalidArgumentException | ParseException e) {
            log.error("处理MESSAGE请求异常", e);
        }
    }

    /**
     * 处理Broadcast语音广播的Message请求
     *
     * @param evt
     */
    private void processMessageBroadcast(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            Element deviceIdElement = rootElement.element("DeviceID");
            String deviceId = deviceIdElement.getText();
            Device device = DeviceCache.getDevice(deviceId);
            Long did = device.getId();
            //判断是否有语音广播的命令缓存，如果有则回复200OK，并且刷新缓存过期时间
            if (CommandManager.getWithBroadcast(deviceId) != null) {
                //回复 200 OK
                responseAck(evt);
            } else {
                //没有说明命令已过期
                responseAck(evt, Response.BAD_REQUEST);
            }
        } catch (Exception e) {
            log.error("响应Message的Broadcast异常", e);
        }
    }

    /**
     * 处理MobilePosition移动位置消息
     *
     * @param evt
     */
    private void processMessageMobilePosition(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            String deviceId = getText(rootElement, "DeviceID");
            Device device = DeviceCache.getDevice(deviceId);
            if (device == null) {
                log.error("Message接收Catalog数据时设备不在线，deviceId:{}", deviceId);
                return;
            }
            Position position = new Position();
            String longitude = getText(rootElement, "Longitude");
            String latitude = getText(rootElement, "Latitude");
            String speed = getText(rootElement, "Speed");
            String direction = getText(rootElement, "Direction");
            if (NumericUtil.isDouble(longitude)) {
                position.setLongitude(Double.parseDouble(longitude));
            }
            if (NumericUtil.isDouble(latitude)) {
                position.setLatitude(Double.parseDouble(latitude));
            }
            if (NumericUtil.isFloat(speed)) {
                position.setSpeed(Float.parseFloat(speed));
            }
            if (NumericUtil.isShort(direction)) {
                position.setDirect(Short.parseShort(direction));
            }
            // 回复200 OK
            responseAck(evt);
            publisher.onlineEventPublish(device, Constants.EVENT_ONLINE_KEEPLIVE, position);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理DeviceStatus设备状态Message
     *
     * @param evt
     */
    private void processMessageDeviceStatus(RequestEvent evt) {
        try {
            // 回复200 OK
            responseAck(evt);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理DeviceControl设备状态Message
     *
     * @param evt
     */
    private void processMessageDeviceControl(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            String deviceId = getText(rootElement, "DeviceID");
            //String result = XmlUtil.getText(rootElement, "Result");
            // 回复200 OK
            responseAck(evt);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理DeviceConfig设备状态Message
     *
     * @param evt
     */
    private void processMessageDeviceConfig(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            String deviceId = getText(rootElement, "DeviceID");
            // 回复200 OK
            responseAck(evt);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理ConfigDownload设备状态Message
     *
     * @param evt
     */
    private void processMessageConfigDownload(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            String deviceId = getText(rootElement, "DeviceID");
            // 回复200 OK
            responseAck(evt);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理PresetQuery预置位列表Message
     *
     * @param evt
     */
    private void processMessagePresetQuery(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            String deviceId = getText(rootElement, "DeviceID");
            // 回复200 OK
            responseAck(evt);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理DeviceInfo设备信息Message
     *
     * @param evt
     */
    private void processMessageDeviceInfo(RequestEvent evt) {
        try {
            // 回复200 OK
            responseAck(evt);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理catalog响应消息
     *
     * @param evt
     */
    private void processMessageCatalogList(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            // 获取deviceId设备国标id
            Element deviceIdElement = rootElement.element("DeviceID");
            String deviceId = deviceIdElement.getText();

            Device device = DeviceCache.getDevice(deviceId);
            if (device == null) {
                log.error("处理Catalog数据时设备不在线，deviceId:{}", deviceId);
                return;
            }

            // 先回复200 OK，再解析xml文本
            responseAck(evt);

            // 获取设备总通道数量
            Element sumNumElement = rootElement.element("SumNum");
            String sumNum = sumNumElement.getText();
            // 通道数量，去掉报警通道和其他，暂时只有报警通道多余，所以如果有报警通道要去掉报警通道。
            int channelCount = 0;
            if (!StringUtils.isEmpty(sumNum)) {
                channelCount = Integer.parseInt(sumNum);
//                DeviceCache.getDevice(deviceId).setChannelNumber(channelCount);
            }

            // 获取设备信息列表
            Element deviceListElement = rootElement.element("DeviceList");
            if (deviceListElement == null) {
                return;
            }
            // 遍历elements
            List<Element> elements = deviceListElement.elements();
            if (elements != null) {
//                String name = null;
//                String manufacturer = null;
//                String model = null;
//                String channelIdStr = null; // 把这个字符串循环拼接完以后，写入数据库和缓存

//                List<DeviceChannel> deviceChannels = new ArrayList<>(); // 这里不批量添加了，遍历一个判断一个添加一个
//                int serialNum = 1; // 逻辑通道号，从1开始 // 如果是两个通道，每次发一个，就不能从1开始
                for (Element itemDevice : elements) {
                    Element deviceChannelElement = itemDevice.element("DeviceID"); // <Item>标签中的deviceId实际上就是通道id
                    if (deviceChannelElement == null) {
                        continue;
                    }
                    String channelId = deviceChannelElement.getTextTrim(); // 通道id

                    // 如果名称是空的或者是型号是alarmIn，说明不是视频通道，不处理，并且要把总通道数量减1
                    // 本地测试不过滤，模拟34020000001320000066是双通道
                    // 这里废弃了，不适用发上来的通道数量
//                    if (needAlarmIn) {
//                        if (StringUtils.isEmpty(getText(itemDevice, "Name"))
//                                || "AlarmIn".equalsIgnoreCase(getText(itemDevice, "Model"))) {
//                            --channelCount;
//                            continue;
//                        }
//                    }

                    // 如果通道id和设备id一样，就把以下通道的这些属性，赋值给设备，数据库tab_device的值需要初始化。
//                    if (channelId.equals(deviceId)) {
//                        name = getText(itemDevice, "Name"); // 设备名称
//                        manufacturer = getText(itemDevice, "Manufacturer"); // 厂商
//                        model = getText(itemDevice, "Model"); // 型号
//                    }

                    // 这里是把所有cvr管理的设备的视频通道保存到缓存中
                    Set<String> smembers = jedisClusterBSJ.smembers(RedisConstant.CVR_DEVICE_CHANNELS_KEY + deviceId);
                    if (CollectionUtils.isEmpty(smembers)) {
                        smembers = new HashSet<>();
                        smembers.add(channelId);
                    } else {
                        smembers.add(channelId);
                    }
                    jedisClusterBSJ.sadd(RedisConstant.CVR_DEVICE_CHANNELS_KEY + deviceId, smembers.toArray(new String[0]));

                    // 修改DeviceCache的通道号
//                    if (StringUtils.isEmpty(channelIdStr)) {
//                        channelIdStr = channelId;
//                    } else {
//                        // 判断通道号是否已经存在，不存在则添加
//                        String[] split = channelIdStr.split(",");
//                        List<String> channelIds = Arrays.asList(split);
//                        // 如果不包含，追加
//                        if (!channelIds.contains(channelId)) {
//                            channelIdStr += ",";
//                            channelIdStr += channelId;
//                        }
//                    }

                    DeviceChannel deviceChannel = new DeviceChannel();
                    deviceChannel.setDeviceId(deviceId);
                    deviceChannel.setChannelId(channelId);
                    deviceChannel.setName(getText(itemDevice, "Name"));
                    deviceChannel.setManufacture(getText(itemDevice, "Manufacturer"));
                    deviceChannel.setModel(getText(itemDevice, "Model"));
                    // 这里serialNum怎么判断呢，查数据库判断，假设第一次数据库是没有数据的，那就是1，假设已经有了一条，那就是2，以此类推
                    // 先查
                    List<DeviceChannel> deviceChannels = deviceChannelMapper.queryAllChannels(deviceId);
                    int serialNum = deviceChannels.size() + 1;
                    deviceChannel.setSerialNum(serialNum);

                    // 准备将channel添加到数据库，但是如果是cvr发上来的就不添加了
                    if (!StringUtils.isEmpty(cvrIds)) {
                        String[] split = cvrIds.split(",");
                        List<String> cvrIdList = Arrays.asList(split);
                        if (!cvrIdList.contains(deviceId)) { // 如果是cvr发上来的就不管他
                            // 查完以后再判断是添加还是修改
                            if (deviceChannels.size() == 0) {
                                log.info("准备添加新的通道信息，设备id：{}，通道id：{}，逻辑通道号：{}", deviceId, channelId, serialNum);
                                deviceChannelMapper.insert(deviceChannel);
                            } else {
                                boolean exist = false;
                                for (DeviceChannel channel : deviceChannels) {
                                    String deviceId1 = channel.getDeviceId();
                                    String channelId1 = channel.getChannelId();
                                    if (deviceId1.equals(deviceId) && channelId1.equals(channelId)) {
                                        log.info("该设备id和通道id的对应关系已经存在了：deviceId:{},channelId:{}", deviceId, channelId);
                                        exist = true;
                                        break;
                                    }
                                }
                                if (!exist) {
                                    log.info("没有查询到该设备对应的通道信息，新添加deviceId:{}，channelId:{},serialNum:{}", deviceId, channelId, serialNum);
                                    deviceChannelMapper.insert(deviceChannel);
                                }
                            }
                        }
                    }
                }
                Position position = new Position();
                publisher.onlineEventPublish(device, Constants.EVENT_ONLINE_KEEPLIVE, position);
            }
        } catch (Exception e) {
            log.error("响应Message的Catalog异常", e);
        }
    }

    private void addOrUpdateDeviceChannel(List<DeviceChannel> deviceChannels, String deviceId) {
        // 进入到这里，必定要添加或修改通道信息，先查，没有就直接添加，有就先删除了再添加
        List<DeviceChannel> oldDeviceChannels = deviceChannelMapper.queryAllChannels(deviceId);
        if (CollectionUtils.isEmpty(oldDeviceChannels)) {
            deviceChannelMapper.batchAdd(deviceChannels);
        } else {
            for (DeviceChannel deviceChannel : deviceChannels) {
                String newDeviceId = deviceChannel.getDeviceId();
                String newChannelId = deviceChannel.getChannelId();
                Integer serialNum = deviceChannel.getSerialNum();
                // 先查询有没有对应的通道信息，没有的话就添加
                DeviceChannel deviceChannel1 = deviceChannelMapper.selectByDeviceIdAndChannelId(newDeviceId, newChannelId);
                if (deviceChannel1 == null) {
                    deviceChannelMapper.insert(deviceChannel);
                }
            }
        }
    }


    private boolean checkAndUpdateDevice(int channelCount, String channelIdStr, String name, String manufacturer, String model, String deviceId) {
        if (channelIdStr == null) channelIdStr = "";
        if (name == null) name = "";
        if (manufacturer == null) manufacturer = "";
        if (model == null) model = "";
        Device oldDevice = deviceMapper.getDeviceByDeviceId(deviceId);

        String oldDeviceName = oldDevice.getName();
        String oldDeviceManufacturer = oldDevice.getManufacturer();
        String oldDeviceModel = oldDevice.getModel();
        Integer oldChannelNumber = oldDevice.getChannelNumber();
        String oldChannelIdStr = oldDevice.getChannelIdStr();

        // 如果是全部一样的，就不用进行数据库操作，返回false
        if (name.equals(oldDeviceName) && manufacturer.equals(oldDeviceManufacturer) && model.equals(oldDeviceModel)
                /*&& oldChannelNumber == channelCount && channelIdStr.equals(oldChannelIdStr)*/) {
            return false;
        }

        // 直接替换传入的所有字段，因为如果传入的字段为空的话，说明设备也没有发上来，发上来就不会为空，就是对的
        Device device = DeviceCache.getDevice(deviceId);
        device.setName(name);
        device.setManufacturer(manufacturer);
        device.setModel(model);
        device.setChannelNumber(channelCount);
        device.setChannelIdStr(channelIdStr);

        // 更新数据库
        deviceMapper.updateDeviceCatalogInfo(device);
        log.info("修改设备表信息：{}", device.getDeviceId());

        // 更新缓存
        DeviceCache.putDevice(device);

        // 修改了数据库返回true，准备修改通道信息
        return true;

    }

    /***
     * alarm设备报警信息处理
     * @param evt
     */
    private void processMessageAlarm(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            Element deviceIdElement1 = rootElement.element("DeviceID");
            String deviceId = deviceIdElement1.getText();
            Device device = DeviceCache.getDevice(deviceId);
            if (device == null) {
                log.warn("接收到设备报警信息，设备不在线，deviceId={}", deviceId);
                return;
            }
            taskQueue.offer(new SipMsgInfo(evt, device, rootElement));
            // 回复200 OK
            responseAck(evt);

            boolean isEmpty = taskQueue.isEmpty();
            if (!isEmpty) {
                taskExecutor.execute(() -> {
                    log.info("[处理报警通知]等待处理数量：{}", taskQueue.size());
                    List<AlarmReport> alarmReportList = new ArrayList<>();
                    while (!taskQueue.isEmpty()) {
                        try {
                            SipMsgInfo sipMsgInfo = taskQueue.poll();
                            Element deviceIdElement = sipMsgInfo.getRootElement().element("DeviceID");
                            String channelId = deviceIdElement.getText();
                            DeviceAlarm deviceAlarm = new DeviceAlarm();
                            deviceAlarm.setCreateTime(DateUtil.getNow());
                            deviceAlarm.setDeviceId(sipMsgInfo.getDevice().getDeviceId());
                            deviceAlarm.setChannelId(channelId);
                            deviceAlarm.setAlarmPriority(getText(sipMsgInfo.getRootElement(), "AlarmPriority"));
                            deviceAlarm.setAlarmMethod(getText(sipMsgInfo.getRootElement(), "AlarmMethod"));
                            String alarmTime = getText(sipMsgInfo.getRootElement(), "AlarmTime");
                            if (alarmTime == null) {
                                continue;
                            }
                            deviceAlarm.setAlarmTime(DateUtil.ISO8601Toyyyy_MM_dd_HH_mm_ss(alarmTime));
                            String alarmDescription = getText(sipMsgInfo.getRootElement(), "AlarmDescription");
                            if (alarmDescription == null) {
                                deviceAlarm.setAlarmDescription("");
                            } else {
                                deviceAlarm.setAlarmDescription(alarmDescription);
                            }
                            String longitude = getText(sipMsgInfo.getRootElement(), "Longitude");
                            if (longitude != null && NumericUtil.isDouble(longitude)) {
                                deviceAlarm.setLongitude(Double.parseDouble(longitude));
                            } else {
                                deviceAlarm.setLongitude(0.00);
                            }
                            String latitude = getText(sipMsgInfo.getRootElement(), "Latitude");
                            if (latitude != null && NumericUtil.isDouble(latitude)) {
                                deviceAlarm.setLatitude(Double.parseDouble(latitude));
                            } else {
                                deviceAlarm.setLatitude(0.00);
                            }

                            if (!ObjectUtils.isEmpty(deviceAlarm.getDeviceId())) {
                                if (deviceAlarm.getAlarmMethod().contains(DeviceAlarmMethodEnum.Video.getVal() + "")) {
                                    deviceAlarm.setAlarmType(getText(sipMsgInfo.getRootElement().element("Info"), "AlarmType"));
                                }
                            }
                            log.info("[收到报警通知]内容：{}", JSON.toJSONString(deviceAlarm));
                            // 存储报警信息、报警分类
                            AlarmReport alarmReport = new AlarmReport();
                            // todo 填充数据
                            alarmReport.setDeviceName(sipMsgInfo.getDevice().getDeviceId());
                            alarmReport.setNikeName(DeviceCache.getDevice(sipMsgInfo.getDevice().getDeviceId()).getNickName());
                            alarmReport.setDeviceType(4); // GB28181设备
                            alarmReport.setAlarmType(getCustomAlarmType(deviceAlarm.getAlarmMethod(), deviceAlarm.getAlarmType()));
                            alarmReport.setBeginTime(DateUtils.strToDate(deviceAlarm.getAlarmTime()));
                            alarmReport.setEndTime(DateUtils.strToDate(deviceAlarm.getAlarmTime()));
                            alarmReport.setRemark(deviceAlarm.getAlarmDescription());
                            alarmReport.setAlarmPriority(deviceAlarm.getAlarmPriority());
                            alarmReport.setAlarmMethod(deviceAlarm.getAlarmMethod());
                            alarmReport.setLongitude(deviceAlarm.getLongitude());
                            alarmReport.setLatitude(deviceAlarm.getLatitude());


                            // 将报警数据推送到前端
                            //报警提醒
                            WebSocketMsgVO webSocketMsgVO = WebSocketMsgVO.builder()
                                    .msgType(NumericalConstants.THREE)
                                    .obj(alarmReport)
                                    .build();
                            baseMqPushUtil.sendTopicBytes(MqTopicAndTagConstants.SYSTEM_WEB_SERVER_CHANGE_TOPIC,
                                    MqTopicAndTagConstants.SYSTEM_WEB_PRODUCT_CHANGE_TAG, JSON.toJSONBytes(webSocketMsgVO));

                            //发送消息通知组合报警
                            baseMqPushUtil.sendTopicBytes(MqTopicAndTagConstants.BASE_SERVER_PLS_CHANGE_TOPIC,
                                    MqTopicAndTagConstants.BASE_CHANGE_PLS_UPDATE_TAG, JSON.toJSONBytes(alarmReport));

                            // 如果是设备故障报警，redis写入设备故障的标志，将故障记录写入到故障报表
                            if (!StringUtil.isEmpty(alarmReport.getAlarmMethod()) && alarmReport.getAlarmMethod().contains("6")) {
                                String s = jedisClusterBSJ.get(RedisConstant.REDIS_DEVICE_INFO + deviceId);
                                if (s != null) {
                                    RedisDeviceInfo redisDeviceInfo = JSON.parseObject(s, RedisDeviceInfo.class);
                                    redisDeviceInfo.setFault(1);
                                    jedisClusterBSJ.set(RedisConstant.REDIS_DEVICE_INFO + deviceId, JSON.toJSONString(redisDeviceInfo));
                                }
                                Fault fault = new Fault();
                                fault.setDeviceNumber(deviceId);
                                fault.setNickName(device.getNickName());
                                fault.setFaultType(3);
                                Date date = new Date();
                                fault.setFaultDate(date);
                                fault.setCreateTime(date);
                                fault.setHandle(0);
                                faultMapper.insert(fault);

                            }

                            //报警附件异步创建
                            Date sDate = DateUtils.strToDate(deviceAlarm.getAlarmTime());
                            long first = sDate.getTime() - 30000;
                            String firstHalfMinute = DateUtils.dateToStr(new Date(first));
                            String filePath = FileCreateUtil.filePath(sDate, annexPath,
                                    FileTypeEnum.VIDEO_ALARM.getFileType(), device.getId());
                            SdkAnnex sdkAnnex = SdkAnnex.builder()
                                    .devId(device.getId()).startTime(firstHalfMinute)
                                    .endTime(deviceAlarm.getAlarmTime()).path(filePath).build();
                            baseMqPushUtil.sendTopicBytes(MqTopicAndTagConstants.SDK_FILE_SERVER_TOPIC,
                                    MqTopicAndTagConstants.SDK_FILE_DOWNLOAD_TAG, JSON.toJSONBytes(sdkAnnex));
                            alarmReport.setFilePath(filePath);

                            alarmReportList.add(alarmReport);

                            // 把视频报警记录到日志中

                            SysRecordLog sysRecordLog = new SysRecordLog();
                            sysRecordLog.setStatus(0);
                            sysRecordLog.setId(LOG_ID);
                            sysRecordLog.setRecordTime(System.currentTimeMillis());
                            //请求的IP
//                            String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
                            sysRecordLog.setRecordIp("");
                            sysRecordLog.setRecordUrl("");
//                            TokenUser tokenInfo = StpLoginUserUtil.getTokenUser();
//                            if (tokenInfo == null) {
//                                tokenInfo = StpLoginUserUtil.getTokenUserBySaToken(getSessionId(joinPoint.getArgs()));
//                            }
                            sysRecordLog.setRecordId(2L);
                            sysRecordLog.setRecordType(0);
                            sysRecordLog.setRecordName("admin");
                            //设置方法名称
                            sysRecordLog.setMethod("");
                            //设置请求方式
                            sysRecordLog.setRequestMethod("");
                            // 设置action动作
                            sysRecordLog.setBusinessType(BusinessType.ALARM_EXCEPTION_NOTIFY.ordinal());
                            // 设置标题
                            sysRecordLog.setTitle(SysLogTitleConst.ALARM_EXCEPTION_NOTIFY);
                            // 设置描述
                            sysRecordLog.setDesc("视频设备报警异常通知");
                            //存储
                            log.info("视频设备报警日志保存内容: {}", JSON.toJSONString(sysRecordLog));
                            OperateLogSaveQueue.push(JSON.toJSONBytes(sysRecordLog));


                        } catch (Exception e) {
                            log.error("解析报警信息出错：deviceId={}", deviceId);
                        }
                        if (alarmReportMapper == null) {
                            alarmReportMapper = SpringUtil.getBean(AlarmReportMapper.class);
                        }
                        alarmReportMapper.addAll(alarmReportList);
                    }
                });
            }


        } catch (DocumentException | SipException | InvalidArgumentException | ParseException e) {
            // } catch (DocumentException e) {
            e.printStackTrace();
        }
    }


    /**
     * 将国标的报警类型转换为自定义的报警类型AlarmEnum
     *
     * @param alarmMethod
     * @param alarmType
     * @return
     */
    private Integer getCustomAlarmType(String alarmMethod, String alarmType) {
        if (StringUtil.isEmpty(alarmMethod)) {
            return null;
        }
        if (alarmMethod.contains("2")) {
            if (StringUtil.isEmpty(alarmType)) {
                return AlarmEnum.ALARM_DEVICE_ALARM.getCode();
            }
            if ("1".equals(alarmType)) {
                return AlarmEnum.VIDEO_LOSS_ALARM.getCode();
            }
            if ("2".equals(alarmType)) {
                return AlarmEnum.DEVICE_TAMPER_ALARM.getCode();
            }
            if ("3".equals(alarmType)) {
                return AlarmEnum.STORAGE_DEVICE_DISK_FULL_ALARM.getCode();
            }
            if ("4".equals(alarmType)) {
                return AlarmEnum.DEVICE_HIGH_TEMPERATURE_ALARM.getCode();
            }
            if ("5".equals(alarmType)) {
                return AlarmEnum.DEVICE_LOW_TEMPERATURE_ALARM.getCode();
            }
        }
        if (alarmMethod.contains("5")) {
            if ("1".equals(alarmType)) {
                return AlarmEnum.MANUAL_VIDEO_ALARM.getCode();
            }
            if ("2".equals(alarmType)) {
                return AlarmEnum.MOTION_TARGET_DETECTION_ALARM.getCode();
            }
            if ("3".equals(alarmType)) {
                return AlarmEnum.RESIDUAL_DETECTION_ALARM.getCode();
            }
            if ("4".equals(alarmType)) {
                return AlarmEnum.OBJECT_REMOVAL_DETECTION_ALARM.getCode();
            }
            if ("5".equals(alarmType)) {
                return AlarmEnum.WIRE_TRIPPING_DETECTION_ALARM.getCode();
            }
            if ("6".equals(alarmType)) {
                return AlarmEnum.INTRUSION_DETECTION_ALARM.getCode();
            }
            if ("7".equals(alarmType)) {
                return AlarmEnum.REVERSE_DETECTION_ALARM.getCode();
            }
            if ("8".equals(alarmType)) {
                return AlarmEnum.WANDERING_DETECTION_ALARM.getCode();
            }
            if ("9".equals(alarmType)) {
                return AlarmEnum.TRAFFIC_STATISTICS_ALARM.getCode();
            }
            if ("10".equals(alarmType)) {
                return AlarmEnum.DENSITY_DETECTION_ALARM.getCode();
            }
            if ("11".equals(alarmType)) {
                return AlarmEnum.VIDEO_ANOMALY_DETECTION_ALARM.getCode();
            }
            if ("12".equals(alarmType)) {
                return AlarmEnum.QUICK_MOVING_ALARM.getCode();
            }
        }
        if (alarmMethod.contains("6")) {
            if ("1".equals(alarmType)) {
                return AlarmEnum.DISK_FAILURE_ALARM.getCode();
            }
            if ("2".equals(alarmType)) {
                return AlarmEnum.FAN_FAULT_ALARM.getCode();
            }
        }
        return null;
    }

    /***
     * 收到keepalive请求 处理
     *
     * @param evt
     */
    private void processMessageKeepAlive(RequestEvent evt) {
        try {
            Element rootElement = getRootElement(evt);
            String deviceId = getText(rootElement, "DeviceID");
            Device device = DeviceCache.getDevice(deviceId);

            log.info("接收到设备keepAlive消息，deviceId={}", deviceId);

            if (device != null) {
                // 不为空，说明注册在线，回复200 OK
                responseAck(evt);
//                log.info("KeepAlive消息回复200 OK， deviceId：{}", deviceId);
                //更新在线状态
                publisher.onlineEventPublish(device, Constants.EVENT_ONLINE_KEEPLIVE, null);
            } else {
                //这里是没有设备信息，让他重新注册
                log.warn("收到设备保活消息时没有找到设备缓存信息，重新写入，deviceId={}", deviceId);
                Request request = evt.getRequest();

                FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
                AddressImpl address = (AddressImpl) fromHeader.getAddress();
                ViaHeader viaHeader = (ViaHeader) request.getHeader(ViaHeader.NAME);
                int rPort = viaHeader.getRPort();
                String received = viaHeader.getReceived();
                SipUri uri = (SipUri) address.getURI();
                deviceId = uri.getUser();
                device = new Device();
                device.setStreamMode("UDP");
                device.setDeviceId(deviceId);
                device.setIp(received);
                device.setPort(rPort);
                device.setChannelIdStr(deviceId);
                //获取外网的ip和端口
                RequestEventExt eventExt = (RequestEventExt) evt;
                String ipAddress = eventExt.getRemoteIpAddress();
                SIPRequest sipRequest = (SIPRequest) evt.getRequest();
                int port = sipRequest.getPeerPacketSourcePort();
                device.setHostAddress(ipAddress.concat(":").concat(String.valueOf(port)));
                device.setTransport("UDP");
                Response response = getMessageFactory().createResponse(Response.OK, request);
                //设置设备的外网端口
                setRport(evt, response);
//                log.info("注册成功! deviceId:" + device.getDeviceId());
                device.setRegisterTime(DateUtils.dateToStr(new Date()));
                publisher.onlineEventPublish(device, Constants.EVENT_ONLINE_REGISTER, null);

                //Request request = evt.getRequest();
                //Response
                response = getMessageFactory().createResponse(Response.BAD_REQUEST, request);
                new DigestServerAuthenticationHelper().generateChallenge(getHeaderFactory(), response, sipConfig.getSipDomain());
                //设置设备的外网端口
                setRport(evt, response);
                //响应设备
                getServerTransaction(evt).sendResponse(response);
                log.info("KeepAlive消息回复400， deviceId：{}", deviceId);

//                String finalDeviceId = deviceId;
//                new Thread(() -> {
//                    try {
//                        Thread.sleep(5000);
//                    } catch (InterruptedException exception) {
//                        exception.printStackTrace();
//                    }
//                    sendBye(finalDeviceId);
//
//                }).start();

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 设备发保活消息时没有获取到缓存信息，从redis获取session，先给设备发送关闭直播和回放的指令
//    private void sendBye(String deviceId) {
//        int[] channelSerialArr = {1};
//        String[] cmdTypeArr = {Constants.PLAYER_PREFIX, Constants.PLAY_BLACK_PREFIX};
//        for (String s : cmdTypeArr) {
//            for (int i : channelSerialArr) {
//                String hget = jedisClusterBSJ.hget(RedisConstant.VIDEO_STREAM_SESSION + deviceId, s + "_" + i);
//                if (hget != null) {
//                    VideoStreamSession session = JSON.parseObject(hget, VideoStreamSession.class);
//                    cmder.streamByeCmdAfterRestart(deviceId, s, i, session);
//                    log.info("发送关闭指令deviceId:{}, cmdType:{},channelSerial:{},session:{}",deviceId, s, i, session.toString());
//                }
//            }
//        }
//    }

    /***
     * 处理RecordInfo设备录像列表Message请求
     *
     * @param evt
     */
    private void processMessageRecordInfo(RequestEvent evt) {
        try {
            // 回复200 OK
            responseAck(evt);
            //获取MQ命令缓存
            Element rootElement = getRootElement(evt);
            //命令序列号
            String sn = getText(rootElement, "SN");
            Element deviceIdElement = rootElement.element("DeviceID");
            String deviceId = deviceIdElement.getText();                        //这个deviceId很诡异，根据不同设备可能是设备号，也可能是通道号，不能做判断标准，采用RecordInfo+sn号做缓存

//            // 如果回放是通过cvr的话，cvr里的通道是不能重复的，那么如果这个是设别id直接可以获取到cache的设备信息，
//            // 如果是通道id的话，就通过通道id查询出来对应的设备id
//            Device deviceCache = DeviceCache.getDevice(deviceId);
//            if (deviceCache == null) {
//                List<DeviceChannel> deviceChannels = deviceChannelMapper.queryAllByChannelId(deviceId);
//                if (deviceChannels != null) {
//                    if (deviceChannels.size() == 1) { // 一个是正常的，两个的话打个日志看下
//                        DeviceChannel deviceChannel = deviceChannels.get(0);
//                        deviceCache = DeviceCache.getDevice(deviceChannel.getDeviceId());
//                    } else {
//                        // 找不到设备信息就不处理了。
//                        log.error("处理录像文件，根据通道查询到的设备数量不是1，查通道的参数是：{}，查询结果是:{}", deviceId, deviceChannels.toString());
//                    }
//                }
//            }
//            if (deviceCache == null) {
//                return;
//            }

            Command cache = CommandManager.get("RecordInfo", sn);
            if (cache == null) {
                //MQ命令缓存为空，大概率是超时了
                log.error("处理RecordInfo时无法找到对应的uuid，deviceId:{}，sn:{}", deviceId, sn);
                //直接不处理解析了，以为没法响应mq
                return;
            } else {
                //先清除缓存         //这里删掉了就不能分多次接收了，蠢逼
                //CommandManager.remove(deviceId, sn);
            }


            //设备/区域名称
            String name = getText(rootElement, "Name");
            //查询结果总数
            int total = 0;
            if (StringUtils.isNotBlank(getText(rootElement, "SumNum"))) {
                total = Integer.parseInt(getText(rootElement, "SumNum"));
            }

            Element recordListElement = rootElement.element("RecordList");
            if (recordListElement == null || total == 0) {
                //无录像数据
                RecordEmpty listData = new RecordEmpty();
                listData.setStatus(0);
                listData.setInfo("录像文件总数是零");
                listData.setRecords(0);
                listData.setSession(cache.getUuid());
                log.info("uuid:" + cache.getUuid() + " 录像文件总数是零");

                // 记录指令收发日志
                CmdLog cmdLog = new CmdLog();
                cmdLog.setDeviceId(deviceId);
                cmdLog.setDeviceName("");
                String desc = CmdLogEnum.TYPE_RECORD_QUERY.getDesc();
                cmdLog.setType(desc);
                cmdLog.setState(CmdLogEnum.STATE_RECEIVE.getDesc());
                cmdLog.setContent("接收到设备发送的录像文件列表，录像文件总数是0");
                cmdLog.setTime(new Date());
                cmdLogMapper.insert(cmdLog);

            } else {
                //有录像数据
                // 获取国标通道id
                String channelId = "";

                RecordList listData = new RecordList();
                listData.setStatus(1);
                listData.setInfo("OK");
                listData.setRecords(total);
                listData.setSession(cache.getUuid());
                Iterator<Element> recordListIterator = recordListElement.elementIterator();
                if (recordListIterator != null) {
                    RecordFile record = new RecordFile();
                    log.info("处理录像列表数据...");
                    // 遍历DeviceList
                    while (recordListIterator.hasNext()) {
                        Element itemRecord = recordListIterator.next();
                        Element recordElement = itemRecord.element("DeviceID");
                        if (recordElement == null) {
                            log.info("记录为空，下一个...");
                            continue;
                        }
                        if (StringUtils.isEmpty(channelId)) {
                            channelId = deviceIdElement.getText();
                        }
                        record = new RecordFile();
                        record.setDeviceId(cache.getDeviceId());
                        record.setChannelId(channelId);
                        //暂时默认通道1
                        record.setChannel(Byte.parseByte(cache.getChannel()));
                        record.setBegintime(DateUtil.ISO8601ToTimestamp(getText(itemRecord, "StartTime")));
                        record.setEndtime(DateUtil.ISO8601ToTimestamp(getText(itemRecord, "EndTime")));
                        listData.setRecordFile(record);
                    }
                }
                if (listData.getRecordFileSize() >= total) {
                    if (listData.getRecordFileSize() > total) {
                        //一般不可能出现这种情况
                        log.info("已获取的录像记录数量{}，大于需要的录像记录数量{}", listData.getRecordFileSize(), total);
                    } else {
                        //已经接收完整
                        log.info("已获取的录像记录数量{}，等于需要的录像记录数量{}", listData.getRecordFileSize(), total);
                    }
                    // 有录像文件就存入redis，带过期时间 五分钟
                    RecordInfoUtil.saveRecordInfoToRds(channelId, listData, jedisClusterBSJ, 600);

                    CommandManager.remove("RecordInfo", sn);
                } else {
                    //接收不完整，开启一个线程汇总
                    // 改用单独线程统计已获取录像文件数量，避免多包并行分别统计不完整的问题
                    String key = "RecordInfo" + "_" + sn;
                    RecordList recordList = recordListMap.putIfAbsent(key, listData);
                    if (null == recordList) {
                        //开启线程
                        CheckForAllRecordsThread chk = new CheckForAllRecordsThread(key, listData.getRecords(), jedisClusterBSJ, channelId);
                        chk.start();
                    } else {
                        //已经开启了线程，将录像信息加进缓存
                        log.info("回放列表放入线程，");
                        recordList.addRecordFiles(listData.getListFile());
                    }
                }

                // 记录指令收发日志
                CmdLog cmdLog = new CmdLog();
                cmdLog.setDeviceId(deviceId);
                cmdLog.setDeviceName("");
                String desc = CmdLogEnum.TYPE_RECORD_QUERY.getDesc();
                cmdLog.setType(desc);
                cmdLog.setState(CmdLogEnum.STATE_RECEIVE.getDesc());
                cmdLog.setContent("接收到设备发送的录像文件列表，共{ " + total + " }项，当前{" + listData.getRecordFileSize() + "}项");
                cmdLog.setTime(new Date());
                cmdLogMapper.insert(cmdLog);

            }
        } catch (Exception e) {
            log.error("处理RecordInfo设备录像列表Message请求异常", e);
        }
    }

    /**
     * 收到MediaStatus消息处理
     *
     * @param evt
     */
    private void processMessageMediaStatus(RequestEvent evt) {
        try {
            // 回复200 OK
            responseAck(evt);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private Element getRootElement(RequestEvent evt) throws DocumentException {
        Request request = evt.getRequest();
        SAXReader reader = new SAXReader();
        reader.setEncoding("gbk");
        Document xml = reader.read(new ByteArrayInputStream(request.getRawContent()));
        return xml.getRootElement();
    }

    public void setCmder(SIPCommander cmder) {
        this.cmder = cmder;
    }


    public void setDeferredResultHolder(DeferredResultHolder deferredResultHolder) {
        this.deferredResultHolder = deferredResultHolder;
    }

    public void setPublisher(EventPublisher publisher) {
        this.publisher = publisher;
    }

    public void setCommandUpProducer(CommandUpProducer commandUpProducer) {
        this.commandUpProducer = commandUpProducer;
    }

    public void setSipConfig(SipConfig sipConfig) {
        this.sipConfig = sipConfig;
    }

    public void setJedisClusterBSJ(JedisClusterBSJ jedisClusterBSJ) {
        this.jedisClusterBSJ = jedisClusterBSJ;
    }

    public void setTaskExecutor(Executor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    public void setAlarmReportMapper(AlarmReportMapper alarmReportMapper) {
        this.alarmReportMapper = alarmReportMapper;
    }

    public void setDeviceMapper(DeviceMapper deviceMapper) {
        this.deviceMapper = deviceMapper;
    }

    public void setBaseMqPushUtil(BaseMqPushUtil baseMqPushUtil) {
        this.baseMqPushUtil = baseMqPushUtil;
    }

    public void setFaultMapper(FaultMapper faultMapper) {
        this.faultMapper = faultMapper;
    }

    public void setCvrIds(String cvrIds) {
        this.cvrIds = cvrIds;
    }

    public void setDeviceChannelMapper(DeviceChannelMapper deviceChannelMapper) {
        this.deviceChannelMapper = deviceChannelMapper;
    }

    public void setNeedAlarmIn(boolean needAlarmIn) {
        this.needAlarmIn = needAlarmIn;
    }

    public void setCmdLogMapper(CmdLogMapper cmdLogMapper) {
        this.cmdLogMapper = cmdLogMapper;
    }

}